<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>nio比传统io有什么优势</title>
    <style>
        body{background-color: #c7edcc}
    </style>
</head>
<body>
<pre>

    这个问题需要从Unix五种io模型说起

    当网络服务器面对多个socket连接的时候,当任意一个socket发来数据,服务器都必须及时读取请求,解码请求,处理请求,编码响应,发送响应消息.

    想要实现及时处理socket发送来的请求,可以采用以下方法:

    1.采用非阻塞模式,服务器可以遍历所有socket连接,检查是否有数据准备好(非阻塞系统调用recv(),如果没有数据准备好,系统会返回一个错误码),
    但每一次检查都会又一次系统调用,不停的循环系统调用开销是巨大的.

    2.采用阻塞模型,为每个socket采用一个线程,当调用recv()时,阻塞导致线程挂起,当有数据到来时线程被唤醒,从系统临时缓冲区拷贝数据到应用程序中,
    这种情况下线程起到一个数据到达的监视器,但这种情况在有大量连接的情况下会创建很多线程,每个线程都有自己的线程栈和指针等数据结构,所以开销很大
    ,而且线程调度和上下文切换.

    3.采用IO复用模型,利用操作系统提供的就绪选择功能,操作系统一次性告诉应用程序那些socket有数据来了,这样子只需要阻塞一个线程就可以监听所有socket
    效率高.

    java nio几个主要的构成组件

    选择器(Selector) 选择器类管理着一个被注册的通道集合的信息和它们的就绪状态。通道是和选择器一起被注册的，并且使用选择器来更新通道的就绪状态.

    可选择通道(SelectableChannel) SelectableChannel可以被注册到Selector对象上，同时可以指定对那个选择器而言，那种操作是感兴趣的。
                                 一个通道可以被注册到多个选择器上，但对每个选择器而言只能被注册一次。

    选择键(SelectionKey) 选择键封装了特定的通道与特定的选择器的注册关系。选择键对象被SelectableChannel.register( ) 返回并提供一个表示这种注册关系的标记。
                        选择键包含了两个比特集（以整数的形式进行编码），指示了该注册关系所关心的通道操作，以及通道已经准备好的操作。

    内部主要数据结构:
    选择器(Selector):已注册的键的集合(keys方法),已选择键的集合(selectKeys方法),已取消的键的集合(内部属性),前Set两个不是线程安全的.如果有多个线程访问选择器
                     keys,selectKeys,...需要使用锁同步访问,必须一致的访问顺序,否则会死锁的.

    可选择通道(SelectableChannel):已注册键的数组,一个channel可注册到不同的selector,但每个selector只可以注册一次

    选择键(SelectionKey):包含channel和selector的引用,还有一个interestOpt集合和readyOpt


    将通道注册到选择器上
    为了建立监控三个Socket通道的选择器，您需要做像这样的事情:
    Selector selector = Selector.open( );
    channel1.register (selector, SelectionKey.OP_READ);
    channel2.register (selector, SelectionKey.OP_WRITE);
    channel3.register (selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
    // Wait up to 10 seconds for a channel to become ready
    readyCount = selector.select (10000);
    在SelectableChannel上调用register(selector,ops,obj),会使用selector的register方法创建一个SelectionKey

    管理选择键 一旦一个选择器将一个键添加到它的已选择的键的集合中，它就不会移除这个键。并且，一旦一个键处于已选择的键的集合中，
              这个键的ready集合将只会被设置，而不会被清理.需要程序员读取数据后手动更新key的状态.清理一个SelectKey的ready集合的方式是将这个键从已选择的键的集合中移除
              只要键的通道有感兴趣的事件发生,如果该键不在已选择列表中,该键的ready集合将会被清空


    为什么不直接调用selector的register方法呢?
        因为SelectableChannel可以注册到多个selector上,所以channel中有个属性SelectionKey数组,用于记录注册关系,在channel上调用注册方法,内部调用selector的注册方法,
        一次调用顺便添加到自己的键数组中,方便程序员编码.

    为什么通道需要注册到选择器上?

    在JDK 1.4中，通道有四种被定义的可选择操作：读(read)，写(write)，连接(connect)和接受(accept)。
    accept只有ServerSocketChannel有效,因为只有服务器channel可以接收连接

    Selector是对系统调用select()一个封装,但做了其它工作,例如内部包含已注册的键的集合,已选择的键的集合,已取消的键的集合,当每次执行选择时,执行以下操作:
    1.检查已取消的键的集合,如果非空,将要将改建从另外两个集合中移除,然后再清空自身
    2.执行选择,已注册的键的集合中的键的interest集合将被检查.所有通道没有就绪当前线程挂起,操作系统发现有通道对interest感兴趣的操作就绪,
        a.如果该通道在不在选择列表中,那么该键的ready集合被清空,然后被设置成当前操作系统发现的就绪操作
        b.在选择列表中,直接用操作系统发现的就绪操作更新器ready集合,以前的值不会被清除
    select操作返回的值是ready集合在select中被修改的键的数量，而不是已选择的键的集合中的通道的总数。返回值不是已准备好的通道的总数，
    而是从上一个select()调用之后进入就绪状态的通道的数量

    使用一个线程来服务所有通道?
        使用一个线程来服务所有通道,对于单个cpu服务器来说是个好方案,对于多cpu这样效率太低.空闲很多cpu资源.比如在一个一个服务器服务大量分布式
    传感器,使用单线程来服务,当单个服务器线程在为通道服务时,新建立连接将需要等待,在实时应用程序中这是不可接受的


</pre>

</body>
</html>